package org.example.filter;

import cn.hutool.json.JSONUtil;
import io.jsonwebtoken.Claims;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import org.example.exception.CusAuthenticationException;
import org.example.model.vo.LoginUser;
import org.example.util.JwtUtil;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;

import java.util.Date;

@Component
@RequiredArgsConstructor
public class JwtAuthenticationWebFilter implements WebFilter {

    private final ReactiveRedisTemplate<String, String> reactiveRedisTemplate;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getPath().value();

        // 跳过登录端点
        if (path.endsWith("/sso/login")) {
            return chain.filter(exchange);
        }

        String authHeader = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
            return Mono.error(new CusAuthenticationException("缺少有效的认证令牌"));
        }

        String token = authHeader.substring(7);

        return validateToken(token)
                .flatMap(claims -> processClaims(exchange, chain, token, claims))
                .onErrorResume(e -> handleAuthenticationError(exchange, e));
    }

    private Mono<Claims> validateToken(String token) {
        return Mono.fromCallable(() -> {
                    Claims claims = JwtUtil.parseJWT(token);
                    if (claims == null) {
                        throw new CusAuthenticationException("无效的令牌");
                    }
                    if (isExpired(claims)) {
                        throw new CusAuthenticationException("令牌已过期");
                    }
                    return claims;
                })
                .subscribeOn(Schedulers.boundedElastic()); // 阻塞操作在弹性线程池执行
    }

    private Mono<Void> processClaims(ServerWebExchange exchange,
                                     WebFilterChain chain,
                                     String token,
                                     Claims claims) {
        return checkTokenLogout(token)
                .flatMap(isLogout -> {
                    if (isLogout) {
                        return Mono.error(new CusAuthenticationException("用户已登出，请重新登录"));
                    }

                    LoginUser loginUser = JSONUtil.toBean(claims.getSubject(), LoginUser.class);
                    //exchange.getAttributes().put("loginUser", loginUser);

                    // 创建认证对象
                    Authentication authentication = new UsernamePasswordAuthenticationToken(
                            loginUser, null, loginUser.getAuthorities()
                    );

                    // 设置安全上下文
                    return chain.filter(exchange)
                            .contextWrite(ReactiveSecurityContextHolder.withAuthentication(authentication))
                            .then(Mono.fromRunnable(() -> {
                                // 检查是否需要刷新令牌
                                if (isNeedRefresh(claims)) {
                                    String newToken = JwtUtil.createJWT(JSONUtil.toJsonStr(loginUser));
                                    exchange.getResponse().getHeaders().add("xs-refresh-token", newToken);
                                }
                            }));
                });
    }

    private Mono<Boolean> checkTokenLogout(String token) {
        return reactiveRedisTemplate.hasKey("token:" + token)
                .map(exists -> !exists); // 如果Redis中不存在，表示已登出
    }

    private Mono<Void> handleAuthenticationError(ServerWebExchange exchange, Throwable e) {
        if (e instanceof CusAuthenticationException) {
            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            response.getHeaders().setContentType(org.springframework.http.MediaType.APPLICATION_JSON);

            String errorJson = JSONUtil.toJsonStr(
                    new ErrorResponse(HttpStatus.UNAUTHORIZED.value(), e.getMessage())
            );

            byte[] bytes = errorJson.getBytes(java.nio.charset.StandardCharsets.UTF_8);
            return response.writeWith(Mono.just(response.bufferFactory().wrap(bytes)));
        }
        return Mono.error(e);
    }

    private boolean isExpired(Claims claims) {
        Date expiration = claims.getExpiration();
        return expiration.before(new Date());
    }

    private boolean isNeedRefresh(Claims claims) {
        Date expiration = claims.getExpiration();
        Date refreshTime = new Date(expiration.getTime() - 1000 * 60 * 30L);
        return refreshTime.before(new Date());
    }

    @AllArgsConstructor
    private static class ErrorResponse {
        private final int code;
        private final String message;
        private final long timestamp = System.currentTimeMillis();

        // Getter 方法需要添加以便JSON序列化
        public int getCode() { return code; }
        public String getMessage() { return message; }
        public long getTimestamp() { return timestamp; }
    }
}
